package cn.yangliu.nacos.oauth2.config;

import cn.yangliu.nacos.oauth2.ex.OAuthException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;

/**
 * The interface Oauth 2 web response exception translator.
 *
 * @author 问道于盲
 * @date 2019 -12-12
 */
@Component
public class Oauth2WebResponseExceptionTranslator
        implements WebResponseExceptionTranslator<OAuth2Exception> {

    /**
     * current class instance's member.
     * The Throwable analyzer.
     */
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    /**
     * Translate response entity.
     *
     * @param e the e
     * @return the response entity
     * @throws Exception the exception
     */
    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        /**
         * Try to extract a SpringSecurityException from the stacktrace
         */
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(
                    new OAuthException(ase.getMessage(), ase, ((OAuth2Exception) ase).getHttpErrorCode()));
        }
        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(
                    new OAuthException(e.getMessage(), e, 401));
        }
        ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(
                    new OAuthException(e.getMessage(), e, 401));
        }
        ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer
                .getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new OAuthException(e.getMessage(), e,405));
        }
        return handleOAuth2Exception(
                new OAuthException(e.getMessage(), e, HttpStatus.INTERNAL_SERVER_ERROR.value()));
    }

    /**
     * handleOAuth2Exception.
     *
     * @param e the e
     * @return the response entity
     */
    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) {
        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }
        return new ResponseEntity<>(e, headers, HttpStatus.valueOf(status));
    }
}
